#include "PracticalSocket.h"  // For Socket, ServerSocket, and SocketException
#include <iostream>           // For cout, cerr
#include <cstdlib>            // For atoi()  
#include <pthread.h>          // For POSIX threads  
#include <boost/algorithm/string.hpp>
#include <locale>
#include <iostream>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
#include "sslclient.h"
#include <fstream>
#include <string>
#include <iostream>
#include "http_sanitize.h"

const int RCVBUFSIZE = 32;

void HandleTCPClient(TCPSocket *sock);     // TCP client handling function
void *ThreadMain(void *arg);               // Main program of a thread

std::string extract(std::string const& payload, std::string const& from, std::string const& to)
{
	size_t start = payload.find(from);
	if(start == std::string::npos)
		return std::string();

	start += from.size();

	size_t end = payload.find(to, start);
	if(end == std::string::npos)
		return std::string();

	return payload.substr(start, end - start);
}

int main(int argc, char *argv[])
{
	if (argc != 2)                   // Test for correct number of arguments
	{
		cerr << "Usage: " << argv[0] << " <Server Port> " << endl;
		exit(1);
	}

	unsigned short echoServPort = atoi(argv[1]);    // First arg:  local port

	try
	{
		TCPServerSocket servSock(echoServPort);   // Socket descriptor for server

		for (;;)        // Run forever
		{
			TCPSocket *clntSock = servSock.accept();

			pthread_t threadID;
			if (pthread_create(&threadID, NULL, ThreadMain,
			                   (void *) clntSock) != 0)
			{
				cerr << "Unable to create thread" << endl;
				exit(1);
			}
		}
	}
	catch (SocketException &e)
	{
		cerr << e.what() << endl;
		exit(1);
	}

	return 0;
}

void HandleTCPClient(TCPSocket *sock)
{

	static int process = 0;
	static int active_thread=0;
	++active_thread;
	std::cout << "Active_thread=" << active_thread << std::endl;
	process++;
	try
	{
//    cout << sock->getForeignAddress() << ":";
	}
	catch (SocketException &e)
	{
		cerr << "Unable to get foreign address" << endl;
	}
	try
	{
//    cout << sock->getForeignPort();
	}
	catch (SocketException &e)
	{
		cerr << "Unable to get foreign port" << endl;
	}
	//  cout << " with thread " << pthread_self() << endl;

	// Send received string and receive again until the end of transmission
	char echoBuffer[RCVBUFSIZE];
	int recvMsgSize;
	std::string fromClient;
	while ((recvMsgSize = sock->recv(echoBuffer, RCVBUFSIZE)) > 0)
	{
		fromClient.append(echoBuffer,recvMsgSize);

		if(boost::algorithm::contains(fromClient, "\r\n\r\n"))
		{
			if(boost::starts_with(fromClient, "GET"))
				break;
			if(boost::starts_with(fromClient, "POST"))
			{
				std::string postDataSize = extract(fromClient, "Content-Length: ", "\r\n");
				size_t i = boost::lexical_cast<size_t>(postDataSize);
				size_t currentPayloadSize = fromClient.size() - (fromClient.find("\r\n\r\n") + 4) ;
				if(currentPayloadSize == i)
				{
					break;
				}
			}
		}
	}


	size_t end = fromClient.find(" HTTP");
	if(end != std::string::npos)
	{
		http_sanitize http;

		http.requestHeaderFromClient(fromClient);

		if(boost::starts_with(fromClient, "POST"))
		{
			size_t postBodyStart = fromClient.find("\r\n\r\n");
			if(postBodyStart != std::string::npos)
			{
				postBodyStart += 4; // We need to add the size of \r\n\r\n
				if(postBodyStart < fromClient.size())
				{
					std::string postData = fromClient.substr(postBodyStart);
					http.requestBodyFromClient(postData);
				}
			}
		}


		sslclient ssl;
		ssl.connect(http);
		std::string fromServer = http.getRequestHeader();
		std::string responseBody = http.getResponseBody();
		sock->send(fromServer.c_str(), fromServer.size());
		sock->send(responseBody.c_str(), responseBody.size());

	}
	// Destructor closes socket
	--active_thread;
	if(active_thread == 0)
		std::cout << "All Client Thread closed" << std::endl;
}

void *ThreadMain(void *clntSock)
{
	// Guarantees that thread resources are deallocated upon return
	pthread_detach(pthread_self());

	// Extract socket file descriptor from argument
	HandleTCPClient((TCPSocket *) clntSock);

	delete (TCPSocket *) clntSock;
	return NULL;
}
